﻿using System;
using System.Text;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NTLib.Core.Pool;
using System.Threading.Tasks;
using System.Linq;
using System.Threading;

namespace NTLib.Core.UTest.Pool
{
    /// <summary>
    /// Summary description for Pool
    /// </summary>
    [TestClass]
    public class ObjectPoolTest
    {
        #region Fields

        private const int STRESS_ITERATION = 10000;
        private const int DELAY_MAX = 10;

        private TestContext testContextInstance;

        #endregion

        #region properties

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #endregion

        #region Methods

        [TestInitialize]
        public void TestInit()
        {
            ObjectPool<PoolTestItem> pool = new ObjectPool<PoolTestItem>();
        }

        [TestMethod]
        [TestCategory("Core.ObjectPool")]
        public void CreationPool()
        {
            ObjectPool<PoolTestItem> pool = new ObjectPool<PoolTestItem>();

            var item = pool.Get<PoolTestItem>();
            Assert.IsNotNull(item);
            Assert.IsInstanceOfType(item, typeof(PoolTestItem));
        }

        [TestMethod]
        [TestCategory("Core.ObjectPool")]
        [ExpectedException(typeof(IndexOutOfRangeException))]
        public void CreationPoolAndMin()
        {
            ObjectPool<PoolTestItem> pool = new ObjectPool<PoolTestItem>();
            pool.MaxItems = 5;
            pool.MinItems = 7;
        }

        [TestMethod]
        [TestCategory("Core.ObjectPool")]
        [ExpectedException(typeof(IndexOutOfRangeException))]
        public void CreationPoolAndMax()
        {
            ObjectPool<PoolTestItem> pool = new ObjectPool<PoolTestItem>();
            pool.MinItems = 7;
            pool.MaxItems = 5;
        }

        [TestMethod]
        [TestCategory("Core.ObjectPool")]
        public void RecyclingItemPool()
        {
            ObjectPool<PoolTestItem> pool = new ObjectPool<PoolTestItem>();

            Guid save = Guid.Empty;
            using (var item = pool.Get<PoolTestItem>())
            {
                Assert.IsNotNull(item);
                Assert.IsInstanceOfType(item, typeof(PoolTestItem));
                Assert.AreEqual(pool.UsedObjectCount, (uint)1);
                save = ((PoolTestItem)item).ID;
            }

            Assert.AreEqual(pool.UsedObjectCount, (uint)0);
            Assert.AreEqual(pool.FreeObjectsCount, (uint)1);

            var itemSec = pool.Get<PoolTestItem>();
            Assert.IsNotNull(itemSec);
            Assert.IsInstanceOfType(itemSec, typeof(PoolTestItem));
            Assert.AreEqual(save, ((PoolTestItem)itemSec).ID);
        }

        [TestMethod]
        [TestCategory("Core.ObjectPool")]
        public void RecyclingItemPoolAndReset()
        {
            ObjectPool<PoolTestItem> pool = new ObjectPool<PoolTestItem>();

            string testStr = "Test";
            Guid save = Guid.Empty;
            using (var item = pool.Get<PoolTestItem>())
            {
                Assert.IsNotNull(item);
                Assert.IsInstanceOfType(item, typeof(PoolTestItem));
                var instItem = ((PoolTestItem)item);
                instItem.Value = testStr;
                save = instItem.ID;
                Assert.AreEqual(instItem.Value, testStr);
            }

            var itemSec = pool.Get<PoolTestItem>();
            Assert.IsNotNull(itemSec);
            Assert.IsInstanceOfType(itemSec, typeof(PoolTestItem));
            var instItemSec = ((PoolTestItem)itemSec);
            Assert.AreEqual(save, instItemSec.ID);
            Assert.AreNotEqual(instItemSec.Value, testStr);
            Assert.IsNull(instItemSec.Value);
        }

        [TestMethod]
        [TestCategory("Core.ObjectPool")]
        public void RecyclingItemPoolAdvanced()
        {
            ObjectPool<PoolTestItem> pool = new ObjectPool<PoolTestItem>();

            IPoolItem item2 = null;
            Guid save1 = Guid.Empty;
            Guid save2 = Guid.Empty;
            using (var item = pool.Get<PoolTestItem>())
            {
                Assert.IsNotNull(item);
                Assert.IsInstanceOfType(item, typeof(PoolTestItem));
                save1 = ((PoolTestItem)item).ID;

                item2 = pool.Get<PoolTestItem>();
                Assert.IsNotNull(item2);
                Assert.IsInstanceOfType(item2, typeof(PoolTestItem));
                save2 = ((PoolTestItem)item2).ID;
            }

            var itemSec = pool.Get<PoolTestItem>();
            Assert.IsNotNull(itemSec);
            Assert.IsInstanceOfType(itemSec, typeof(PoolTestItem));
            Assert.AreEqual(save1, ((PoolTestItem)itemSec).ID);

            item2.Dispose();
            var item2Sec = pool.Get<PoolTestItem>();
            Assert.IsNotNull(item2Sec);
            Assert.IsInstanceOfType(item2Sec, typeof(PoolTestItem));
            Assert.AreEqual(save2, ((PoolTestItem)item2Sec).ID);
        }

        [TestMethod]
        [TestCategory("Core.ObjectPool")]
        public void RecyclingItemPoolAdvancedAndMax()
        {
            ObjectPool<PoolTestItem> pool = new ObjectPool<PoolTestItem>();
            pool.MaxItems = 2;

            IPoolItem item2 = null;
            IPoolItem item3 = null;
            Guid save1 = Guid.Empty;
            Guid save2 = Guid.Empty;
            Guid save3 = Guid.Empty;
            using (var item = pool.Get<PoolTestItem>())
            {
                Assert.IsNotNull(item);
                Assert.IsInstanceOfType(item, typeof(PoolTestItem));
                save1 = ((PoolTestItem)item).ID;

                item2 = pool.Get<PoolTestItem>();
                Assert.IsNotNull(item2);
                Assert.IsInstanceOfType(item2, typeof(PoolTestItem));
                save2 = ((PoolTestItem)item2).ID;

                item3 = pool.Get<PoolTestItem>();
                Assert.IsNotNull(item2);
                Assert.IsInstanceOfType(item2, typeof(PoolTestItem));
                save3 = ((PoolTestItem)item3).ID;

                Assert.AreEqual(pool.UsedObjectCount, (uint)3);
            }

            item2.Dispose();
            item3.Dispose();

            Assert.AreEqual(pool.UsedObjectCount, (uint)0);
            Assert.AreEqual(pool.FreeObjectsCount, (uint)2);

            var itemSec = pool.Get<PoolTestItem>();
            Assert.IsNotNull(itemSec);
            Assert.IsInstanceOfType(itemSec, typeof(PoolTestItem));
            Assert.AreEqual(save1, ((PoolTestItem)itemSec).ID);

            item2.Dispose();
            var item2Sec = pool.Get<PoolTestItem>();
            Assert.IsNotNull(item2Sec);
            Assert.IsInstanceOfType(item2Sec, typeof(PoolTestItem));
            Assert.AreEqual(save2, ((PoolTestItem)item2Sec).ID);

            var item3Sec = pool.Get<PoolTestItem>();
            Assert.IsNotNull(item3Sec);
            Assert.IsInstanceOfType(item3Sec, typeof(PoolTestItem));
            Assert.AreNotEqual(save3, ((PoolTestItem)item3Sec).ID);

        }

        [TestMethod]
        [TestCategory("Core.ObjectPool")]
        public void Stress_TestMonoThread()
        {
            ObjectPool<PoolTestItem> pool = new ObjectPool<PoolTestItem>();
            pool.MaxItems = 50;
            Random rand = new Random(42);
            var randRange = Enumerable.Range(0, STRESS_ITERATION).Select(i => rand.Next(0, DELAY_MAX)).ToArray();
            foreach (var delay in randRange)
            {
                using (var item = pool.Get<PoolTestItem>())
                {
                    Thread.Sleep(delay);
                }
            }

            Assert.IsTrue(pool.FreeObjectsCount == 1 && pool.UsedObjectCount == 0);
        }

        [TestMethod]
        [TestCategory("Core.ObjectPool")]
        public void Stress_TestMultiThread()
        {
            ObjectPool<PoolTestItem> pool = new ObjectPool<PoolTestItem>();
            pool.MaxItems = 50;
            Random rand = new Random(42);
            var randRange = Enumerable.Range(0, STRESS_ITERATION).Select(i => rand.Next(0, DELAY_MAX)).ToArray();

            List<Task> all = new List<Task>();
            Parallel.ForEach(randRange, (delay) =>
            {
                using (var item = pool.Get<PoolTestItem>())
                {
                    Thread.Sleep(delay);

                }
            });
        }
    }

    #endregion
}
